package dovs.phases;

import dovs.*;
import dovs.analysis.*;
import dovs.node.*;
import static dovs.Util.*;

import java.util.*;

/**
 * Compiler phase to perform constant folding.
 */
public aspect ConstantFolding extends DepthFirstAdapter {
	// Methods for querying the constancy of expressions
	public boolean PExp.isConstant() {
		return false;
	}

	@Override
	public boolean AIntConstExp.isConstant() {
		return true;
	}

	@Override
	public boolean AStringConstExp.isConstant() {
		return true;
	}

	@Override
	public boolean ABooleanConstExp.isConstant() {
		return true;
	}

	// Value-preserving duplication
	public PExp PExp.duplicate() {
		throw new InternalCompilerError("Duplication of non-constant: "
				+ getClass().getName());
	}

	@Override
	public PExp AIntConstExp.duplicate() {
		return makeIntConst(this.value);
	}

	@Override
	public PExp AStringConstExp.duplicate() {
		return makeStringConst(this.value);
	}

	@Override
	public PExp ABooleanConstExp.duplicate() {
		return makeBooleanConst(this.value);
	}

	// Type-inserting replace
	private void replace(PExp exp, PExp rep) {
		exp.replaceBy(rep);
		rep.type = exp.type;
	}

	@Override
	public void inAProgram(AProgram program) {
		// Pre-pass to inline all constant-values static final fields
		Set<AFieldDecl> fields = new HashSet<AFieldDecl>();
		for (PSourceFile sf : program.getSourceFiles()) {
			for (PDecl decl : sf.getTypeDecl().getMembers()) {
				if (decl instanceof AFieldDecl) {
					AFieldDecl fieldd = (AFieldDecl) decl;
					if (fieldd.isStatic() && fieldd.isFinal()
							&& fieldd.getInit() != null
							&& !fieldd.getInit().isConstant()) 
					{
						fields.add(fieldd);
					}
				}
			}
		}
		boolean changed;
		do {
			changed = false;
			for (Iterator<AFieldDecl> fit = fields.iterator(); fit.hasNext();) {
				AFieldDecl fieldd = fit.next();
				fieldd.getInit().apply(this);
				if (fieldd.getInit().isConstant()) {
					fit.remove();
					changed = true;
				}
			}
		} while (changed);
	}

	@Override
	public void outAStaticFieldLvalue(AStaticFieldLvalue lvalue) {
		AFieldDecl fieldd = lvalue.field_decl;
		if (fieldd.isFinal() && fieldd.getInit() != null
				&& fieldd.getInit().isConstant()) 
		{
			replace((ALvalueExp) lvalue.parent(), fieldd.getInit().duplicate());
		}
	}

	@Override
	public void outACastExp(ACastExp exp) {
		PExp child = exp.getExp();
		if (exp.getType().isString() && child instanceof AStringConstExp) {
			replace(exp, child);
		}
	}

	public @Override void outAUnopExp(AUnopExp exp) {
		PExp child = exp.getExp();
		switch (exp.getUnop().kindPUnop()) {
		case NEGATE:
			if (child instanceof AIntConstExp) {
				replace(exp, makeIntConst(-((AIntConstExp)child).value));
			}
			break;
		case COMPLEMENT:
			if (child instanceof ABooleanConstExp) {
				replace(exp, makeBooleanConst(!((ABooleanConstExp)child).value));
			}
			break;
		case BOOLEAN_TO_STRING:
			if (child instanceof ABooleanConstExp) {
				replace(exp, makeStringConst(String.valueOf(((ABooleanConstExp)child).value)));
			}
			break;
		case BYTE_TO_STRING:
			if (child instanceof AIntConstExp) {
				replace(exp, makeStringConst(String.valueOf((byte)((AIntConstExp)child).value)));
			}
			break;
		case SHORT_TO_STRING:
			if (child instanceof AIntConstExp) {
				replace(exp, makeStringConst(String.valueOf((short)((AIntConstExp)child).value)));
			}
			break;
		case INT_TO_STRING:
			if (child instanceof AIntConstExp) {
				replace(exp, makeStringConst(String.valueOf(((AIntConstExp)child).value)));
			}
			break;
		case CHAR_TO_STRING:
			if (child instanceof AIntConstExp) {
				replace(exp, makeStringConst(String.valueOf((char)((AIntConstExp)child).value)));
			}
			break;
		case OBJECT_TO_STRING:
			if (child instanceof AStringConstExp) {
				replace(exp, child);
			}
			break;
		}
	}

	@Override
	public void outABinopExp(ABinopExp exp) {
		// Constant folding of binary operations
		// If the expression can be folded to an int,
		// a boolean or a string, the result variable
		// is set to an Integer, Boolean or String object,
		// respectively. Otherwise it is left being null.
		PExp left = exp.getLeft(), right = exp.getRight();
		Object result = null;
		if (left instanceof AIntConstExp && right instanceof AIntConstExp) {
			int leftv = ((AIntConstExp)left).value;
			int rightv = ((AIntConstExp)right).value;
			switch (exp.getBinop().kindPBinop()) {
			case PLUS: 
				result = leftv + rightv; 
				break;
			case MINUS: 
				result = leftv - rightv; 
				break;
			case TIMES: 
				result = leftv * rightv; 
				break;
			case DIVIDE: 
				if (rightv != 0) {
					result = leftv / rightv;
				}
				 break;
			case MODULO: 
				if (rightv != 0) {
					result = leftv % rightv;
				}
				break;
			case AND: 
				result = leftv & rightv; 
				break;
			case OR: 
				result = leftv | rightv; 
				break;
			case XOR: 
				result = leftv ^ rightv; 
				break;
			case EQ: 
				result = leftv == rightv;
				break;
			case NE: 
				result = leftv != rightv; 
				break;
			case LT: 
				result = leftv < rightv; 
				break;
			case LE: 
				result = leftv <= rightv; 
				break;
			case GT: 
				result = leftv > rightv; 
				break;
			case GE: 
				result = leftv >= rightv; 
				break;
			default:
				throw new InternalCompilerError("Invalid op for int folding");
			}
		} else if (left instanceof ABooleanConstExp &&
				right instanceof ABooleanConstExp) {
			boolean leftv = ((ABooleanConstExp)left).value;
			boolean rightv = ((ABooleanConstExp)right).value;
			switch (exp.getBinop().kindPBinop()) {
			case EQ: 
				result = leftv == rightv;
				break;
			case NE: 
				result = leftv != rightv; 
				break;
			case AND: 
				result = leftv & rightv; 
				break;
			case OR: 
				result = leftv | rightv; 
				break;
			case XOR: 
				result = leftv ^ rightv; 
				break;
			case LAZY_AND: 
				result = leftv & rightv; 
				break;
			case LAZY_OR: 
				result = leftv | rightv; 
				break;
			default:
				throw new InternalCompilerError("Invalid op for boolean folding");
			}
		} else if (left instanceof AStringConstExp &&
				right instanceof AStringConstExp) {
			String leftv = ((AStringConstExp)left).value;
			String rightv = ((AStringConstExp)right).value;
			switch (exp.getBinop().kindPBinop()) {
			case CONCAT: 
				result = leftv + rightv; 
				break;
			case AEQ: 
				result = leftv.equals(rightv); 
				break;
			case ANE: 
				result = !leftv.equals(rightv);
				break;
			default:
				throw new InternalCompilerError("Invalid op for string folding");
			}
		}

		// If folding occurred, replace the expression by
		// a constant value.
		if (result instanceof Integer) {
			replace(exp, makeIntConst((Integer)result));
		} else if (result instanceof Boolean) {
			replace(exp, makeBooleanConst((Boolean)result));
		} else if (result instanceof String) {
			replace(exp, makeStringConst((String)result));
		}
	}
}
